fix(miner): claimNextBatch can claim the wrong host's row across two forge hosts - #5594
Conversation
…forge hosts portfolio-queue-manager.js's caps-aware batch claiming encoded queue items into ids via bare repoFullName+identifier, with no apiBaseUrl dimension. Since #5563 scoped the underlying store by (apiBaseUrl, repoFullName, identifier), two hosts can now legitimately share a repoFullName+identifier pair -- and when they do, the engine's selection (which only ever sees the opaque id string) could select either host's item, but claimNextBatch's selectFn always defaulted target.apiBaseUrl to github.com, so batchClaim could mark a DIFFERENT row in_progress than the one actually selected. queueItemId/parseQueueItemId now encode and decode apiBaseUrl as part of the id round-trip, so claimNextBatch always claims the exact row the engine selected, never a same-name row on the wrong host. Also hardens the two already-shipped #5563 migrations (claim-ledger.js, portfolio-queue.js) with INSERT OR IGNORE for their table-rebuild copy step, matching run-state.js's and governor-state.js's later fixes for the same class of bug: a legacy row with an already-invalid status/state value (this store's own read path already fails closed on those) would violate the rebuilt table's CHECK constraint and abort the whole migration, permanently breaking that file, instead of being dropped as the corrupt garbage it already was. Advances #5563.
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
loopover-ui | 719a140 | Commit Preview URL Branch Preview URL |
Jul 13 2026, 07:44 AM |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5594 +/- ##
=======================================
Coverage 95.01% 95.01%
=======================================
Files 573 573
Lines 45534 45538 +4
Branches 14680 14680
=======================================
+ Hits 43263 43268 +5
+ Misses 1520 1519 -1
Partials 751 751
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
Warning 🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨 ⏸️ Gittensory review result - manual review recommendedReview updated: 2026-07-13 07:51:10 UTC
⏸️ Suggested Action - Manual Review Review summary Nits — 5 non-blocking
Concerns raised — review before merging
📋 Copy for AI agents — paste into your coding agent
Review context
Contributor next steps
Signal definitions
[BETA] Chat with GittensoryAsk Gittensory a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://gittensory.aethereal.dev/docs/gittensory-commands 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
…re repoFullName (#5595) deny_rule_proposals' PRIMARY KEY (repo_full_name, id) let two forge hosts (github.com vs. a GitHub Enterprise host, #4784) serving a same-named owner/repo share one proposal row (and its maintainer approval decision). Rebuild the constraint to PRIMARY KEY (api_base_url, repo_full_name, id). This file has no schema-version framework of its own (unlike the package's other local stores) -- it uses a raw DatabaseSync connection, no applySchemaMigrations. Follows governor-state.js's idempotent column-presence-gated rebuild convention instead of introducing a new framework dependency here for the first time. Uses INSERT OR IGNORE for the copy step, matching the fix already applied to every other #5563 migration in this epic: a legacy row with an already-invalid status value (this store's own CHECK-constrained schema already rejects those) is dropped, not a migration-aborting crash. Threads an optional apiBaseUrl through refreshProposals/listProposals/ setProposalStatus/resolveEffectiveRules. initDenyHookSynthesisStore has no real callers yet (feeds the consumption surface #2343 will eventually wire into evaluateDenyHooks; this store owns derivation + audit, not live hook interception), so there is no CLI surface or call site to thread through -- purely the storage-layer fix, mirroring governor_reputation_history's scaffold-only precedent. Closes #5563 (5th and final store: claim-ledger.js in #5576, portfolio-queue.js in #5583, run-state.js in #5585, governor-state.js in #5591, plus the claimNextBatch/migration hardening follow-up in #5594).
Summary
Fixes the blocker flagged in review on #5583:
portfolio-queue-manager.js's caps-aware batch claiming encoded queue items via barerepoFullName+identifier, with noapiBaseUrldimension. Since #5563 scoped the underlying store by(apiBaseUrl, repoFullName, identifier), two forge hosts can now legitimately share arepoFullName+identifierpair — and when they do, the engine's selection (which only ever sees the opaqueidstring) could select either host's item, butclaimNextBatch'sselectFnalways defaultedtarget.apiBaseUrltogithub.com, sobatchClaimcould mark a different rowin_progressthan the one the engine actually selected — a real data-integrity bug, not a safe no-op as I'd originally (incorrectly) documented.queueItemId/parseQueueItemIdnow encode and decodeapiBaseUrlas part of the id round-trip, soclaimNextBatchalways claims the exact row the engine selected, never a same-name row on the wrong host.claim-ledger.js,portfolio-queue.js) withINSERT OR IGNOREfor their table-rebuild copy step, matchingrun-state.js's andgovernor-state.js's (fix(miner): scope run-state by forge host, not bare repoFullName #5585, fix(miner): scope governor_reputation_history by forge host, not bare repoFullName #5591) fixes for the same class of bug: a legacy row with an already-invalidstatus/statevalue (this store's own read path already fails closed on those) would violate the rebuilt table'sCHECKconstraint and abort the whole migration, permanently breaking that file, instead of being dropped as the corrupt garbage it already was.Advances #5563.
Test plan
claimNextBatchclaims the correct host's row when two hosts share a repoFullName+identifier (mutation-tested: reverted thequeueItemIdfix, confirmed the test fails with the expectedinvalid_queue_item_iderror, restored and reconfirmed green)claim-ledger.js/portfolio-queue.jsproveINSERT OR IGNOREdrops a corrupted legacy row instead of crashing the migrationnpx vitest run test/unit/miner-*.test.ts— 1645/1645 passingnpx tsc --noEmitcleannpm run build:minercleanentriesToPortfolioQueueapiBaseUrl-fallback ternary's both branches)